Skip to content

Rework serials in domain::new#691

Open
withjannisNLnetLabs wants to merge 6 commits into
mainfrom
cleanup-serials
Open

Rework serials in domain::new#691
withjannisNLnetLabs wants to merge 6 commits into
mainfrom
cleanup-serials

Conversation

@withjannisNLnetLabs

@withjannisNLnetLabs withjannisNLnetLabs commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

TLDR; Improve existing Serial type with more declarative types; Timestamp, SoaSerial and SeqNumberU32, which represent the underlying data more appropriately.

In DNS the SOA Serial number and the RRSIG inception/expiration have a 32-bit number which represents them. The maximum number is around 4 billion. The number may overflow and therefore special consideration has to be taken to increment and compare such types. The arithmetical definition for comparison, addition, etc. is in RFC1982.


Timestamp Represents the time since epoch in seconds. If the number is larger than u32::MAX it will wrap around. The types 'unit' is seconds because it's in the nature of the represented data. This type should not be used for normale time and date operation because of its special properties for comparison and modification. For RRSIG the value may be represented in the YYYYMMDDHHmmSS format see RFC4034.

The Signature Expiration Time and Inception Time field values MUST be
represented either as an unsigned decimal integer indicating seconds
since 1 January 1970 00:00:00 UTC, or in the form YYYYMMDDHHmmSS in
UTC, where:

Careful consideration has to be taken when defining the Display implementation. Another usage of the type is the Cookie used for DNS messages. This type may be generated from an accurate Timestamp (jiff::Timestamp), for example after calculating the inception and expiration time of a RRSIG record.

  • what Display format should be used for the Timestamp? -> use the seconds representation, because this format is valid in all known cases.

SoaSerial Represents the version of a zone by being present in the SOA records serial field. The version number is mostly used in three different formats; a counter without special meaning, seconds since epoch or the date of the day including a two digit counter in the format YYYYMMDDxx. Additionally to the SOA record the type is used in the ZoneMD record. The Display format is simple because in any case the number representation matches the string representation.

SeqNumberU32 Represents the type described in RFC1982 with a 32-bit number. This type is used for the above types as the underlying, wrapped data structure. This type should not be used directly but may well be public for usage by the end user.

  • define SeqNumberU32 as public or private?

The types must be creatable from the primitive type u32. Timestamp and SoaSerial type have no logical connection and shall not be convertible. The use case for the conversion to the wrapped type is questionable.

  • SoaSerial / Timestamp convertible into SeqSequenceU32 and vise versa implemented?

Desired changes:

  • [new] Add SeqNumberU32, SoaSerial, Timestamp signatures with documentation.
  • [new] Implementation of SeqNumberU32, SoaSerial, Timestamp functionality.
  • [new] Add testing for SeqNumberU32, SoaSerial, Timestamp functionality.
  • [new] Migration of data types to new SoaSerial, Timestamp types.

Important

This PR requires Cascade to change, because the types in Rrisg and Soa change.

@tertsdiepraam tertsdiepraam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some preliminary comments. Good work!

Comment thread src/new/base/serial.rs Outdated
Comment thread src/new/base/serial.rs Outdated
Comment thread src/new/base/serial.rs Outdated
Some(Ordering::Equal)
} else if lhs.abs_diff(rhs) == 1 << 31 {
None
} else if (lhs < rhs) ^ (lhs.abs_diff(rhs) > (1 << 31)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could do with either some explanation or be written out more explicitly. Using the bitwise OR with booleans is a bit surprising, though I think it's correct! Maybe even just a small note about that you're aware that it's bitwise or and not exponentiation already helps 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some usage examples which should illustrate the underlying logic.

Comment thread src/new/base/serial.rs Outdated
Comment thread src/new/base/serial.rs Outdated
Comment thread src/new/base/serial.rs Outdated
Comment thread src/new/base/serial.rs Outdated
Comment thread src/new/base/serial.rs Outdated
Comment thread src/new/base/serial.rs Outdated
Comment thread src/new/base/serial.rs
Comment thread src/new/base/serial.rs Outdated
Comment thread src/new/base/serial.rs Outdated
Comment thread src/new/base/serial.rs Outdated
@withjannisNLnetLabs withjannisNLnetLabs added the new-api Relating to the new API, i.e. `domain::new::*`. label Jul 10, 2026
@withjannisNLnetLabs withjannisNLnetLabs self-assigned this Jul 10, 2026
@withjannisNLnetLabs
withjannisNLnetLabs force-pushed the cleanup-serials branch 6 times, most recently from f146341 to 8393971 Compare July 14, 2026 10:03
@withjannisNLnetLabs
withjannisNLnetLabs marked this pull request as ready for review July 14, 2026 11:15
@withjannisNLnetLabs withjannisNLnetLabs changed the title rough structure of timestamp and soa serial Rework serials in domain::new Jul 14, 2026

@bal-e bal-e left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really cool! Well done @withjannisNLnetLabs! I have a bunch of comments (of course :p), but overall, I really like the functionality and the way you've laid out the code. The commits were nicely organized and easy to review.

Minor nits:

  • You often refer to the [`SoaSerial`] or the [`SeqNumberU32`]; maybe you intend these to be read as "the SOA serial type" and "the SeqNumberU32 type", but since the word "type" does not appear in there, IMO it's better to omit "the" and just say e.g. [`SoaSerial`] commonly follows one of the following strategies ....

  • There are a few typos here and there, e.g. Comparision, huaman. I'm not sure if there's a good automated way to detect these, I suspect spellcheck will not deal well with Rust or Markdown.

Top-level feedback:

They Timestamp and SoaSerial type have no logical connection and shall not be convertible.

I'm not sure that's true; one of the common conventions for the SOA serial is as the number of seconds since the Unix epoch modulo 2^32, exactly like Timestamp. I can see some value in allowing conversions between the two, when users choose to use that convention. To avoid users relying on this convention in the wrong cases, maybe it should be implemented as inherent methods with clear names rather than From/Into.

  • define SeqNumberU32 as public or private?
  • SoaSerial / Timestamp convertible into SeqSequenceU32 and vise versa implemented?

I think SeqNumberU32 should be public; it is an implementation of an RFC and so I'd argue it's more than an implementation detail of SoaSerial / Timestamp. Following that, I think it makes sense to have conversions to and from the wrapped types. These can be simple From / Into impls (while inherent methods that are const would be nice, I don't think there's too much need for them; we can add them if we have a need to).

Comment thread src/new/base/serial.rs Outdated
Comment on lines +61 to +63
/// - Date including counter, on change the number gets set to the current
/// date in the format (`YYYYMMDD00`) if the number is smaller than the
/// previous version, the old serial gets increased by one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the number is smaller than the previous version, the old serial gets increased by one

I don't think this edge case needs to be mentioned in these high-level docs.

Comment thread src/new/base/serial.rs Outdated
Comment on lines +140 to +156
/// Construct [`SoaSerial`] from [`u32`].
///
/// Equivalent to [`SoaSerial::get()`].
impl From<SoaSerial> for u32 {
fn from(_value: SoaSerial) -> u32 {
todo!()
}
}

/// The raw [`u32`] underlying this [`SoaSerial`].
///
/// Equivalent to [`SoaSerial::new()`].
impl From<u32> for SoaSerial {
fn from(_value: u32) -> Self {
todo!()
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The top-level descriptions for these two impls appear to be swapped; the first one should say "the raw u32 underlying this SoaSerial." Note that the references to get() and new() do not need to be swapped.

Comment thread src/new/base/serial.rs Outdated
Comment on lines +162 to +164
/// The [`Timestamp`] stores the seconds since Unix Epoch modulo 2^32. It is
/// used in the [`Rrsig`] to keep track of `inception` and `expiration` time
/// and in the edns [`Cookie`] `timestamp`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • "edns" => "EDNS"?
  • I see you're referring to the timestamp field of Cookie; maybe refer to the method directly, i.e. [`Cookie::timestamp()`]?
  • The same could be done for the RRSIG fields.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linking to the timestamp method is possible, but I think it is not possible to link to certain fields.

Comment thread src/new/base/serial.rs Outdated
Comment on lines +181 to +183
/// [RFC1982]: https://datatracker.ietf.org/doc/html/rfc1982
/// [RFC4034]: https://datatracker.ietf.org/doc/html/rfc4034#section-3.1.5
/// [RFC9018]: https://datatracker.ietf.org/doc/html/rfc9018#name-the-timestamp-sub-field

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried docs added here in the future might reference [RFC4034] or [RFC9018] without realizing they point to specific sections of those documents. Maybe explicitly state them as [RFC 4034, section 3.1.5] and [RFC 9018, "The Timestamp Sub-Field"]?

Comment thread src/new/base/serial.rs
}

/// Underlying seconds since Unix Epoch modulo 2^32.
// This type has a specific unit, therefore the `get()` function was

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a blank // line before this comment so it is more clearly distinguished from the public docs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I swapped the doc comment and the comment to have the doc comment closer to the function.

Comment thread src/new/base/serial.rs Outdated
/// Basic operations performed with a [`SoaSerial`].
/// ```
/// # use domain::new::base::SoaSerial;
/// let soa_serial: SoaSerial = SoaSerial::new(u32::MAX).increment();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe separate the .new() and the .increment() into separate statements so it's easier for the user to see what they should focus on? With the line as it is right now, the user might miss new() and only see increment().

Also, I don't think the : SoaSerial type annotation is necessary. It would be if new() returned something weirder, but that is not the case here.

Comment thread src/new/base/serial.rs
}

/// Measure system time since Unix Epoch modulo 2^32.
/// ```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a blank line before this doc test?

Comment thread src/new/base/serial.rs Outdated
/// let now = Timestamp::now();
/// assert_eq!(
/// now.as_seconds(),
/// (UNIX_EPOCH.elapsed().unwrap().as_secs() % 0x1_0000_0000) as u32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since as u32 automatically performs the modulo operation, do you want to omit the % 0x1_0000_0000?

Also, this doc test is a little bit racy... is there some way to make it deterministically succeed? I really want to avoid spurious failures.

Comment thread src/new/base/serial.rs Outdated
/// or
/// this < other and (this - other) > 2^31
/// // 10 < u32::MAX and u32::MAX - 1 > 2^31
///```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a blank line before the doc test, and a space between /// and ```.

Comment thread src/new/base/serial.rs Outdated
Comment on lines +548 to +562
/// # // Get all the edge cases
/// # assert_eq!(
/// # SeqNumberU32::new(0).partial_cmp(&SeqNumberU32::new((1 << 31) - 1)),
/// # Some(Ordering::Less)
/// # );
///
/// # assert_eq!(
/// # SeqNumberU32::new(0).partial_cmp(&SeqNumberU32::new((1 << 31) + 1)),
/// # Some(Ordering::Greater)
/// # );
///
/// # assert_eq!(
/// # SeqNumberU32::new(0).partial_cmp(&SeqNumberU32::new((1 << 31))),
/// # None
/// # );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these belong in a dedicated test function, not in the doc test.

Comment thread src/new/base/serial.rs
UnsizedCopy,
)]
#[repr(transparent)]
pub struct Timestamp(SeqNumberU32);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add from timestamp

@withjannisNLnetLabs
withjannisNLnetLabs force-pushed the cleanup-serials branch 4 times, most recently from e13e2ec to 95a009f Compare July 16, 2026 14:15
@withjannisNLnetLabs
withjannisNLnetLabs force-pushed the cleanup-serials branch 3 times, most recently from 49a1937 to 89c43a8 Compare July 17, 2026 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-api Relating to the new API, i.e. `domain::new::*`.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants